package base.httpclient;

import org.apache.http.*;
import org.apache.http.client.*;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.conn.*;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.FileEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.*;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.junit.Test;

import javax.net.ssl.SSLException;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 *
 * see    https://www.yeetrack.com/?p=773
 */
public class HttpClientTest {
    /**
     * 1，httpget中直接 输入地址
     */
    @Test
    public void test1() {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet get = new HttpGet("https://www.baidu.com");
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(get);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    /**
     * 2，通过urlBuilder
     */
    @Test
    public void test2() {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        try {
            URI uri = new URIBuilder().setScheme("http").setHost("www.baidu.com")
                    .setPath("index.html").setParameter("name", "zhangsan").setParameter("age", "20")
                    .build();
            HttpGet get = new HttpGet(uri);
            response = httpClient.execute(get);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    /**
     * httpResponse
     */
    @Test
    public void test3() {
        HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
                HttpStatus.SC_OK, "xxxx");

        System.out.println(response.getProtocolVersion());
        System.out.println(response.getStatusLine().getStatusCode());
        System.out.println(response.getStatusLine().getReasonPhrase());
        System.out.println(response.getStatusLine().toString());
    }

    /**
     * response 加head
     */
    public void test4() {
        HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
                HttpStatus.SC_OK, "OK");
        response.addHeader("Set-Cookie",
                "c1=a; path=/; domain=localhost");
        response.addHeader("Set-Cookie",
                "c2=b; path=\"/\", c3=c; domain=\"localhost\"");
        Header h1 = response.getFirstHeader("Set-Cookie");
        System.out.println(h1);
        Header h2 = response.getLastHeader("Set-Cookie");
        System.out.println(h2);
        Header[] hs = response.getHeaders("Set-Cookie");
        System.out.println(hs.length);

        //可以用headIeterator 迭代
        HeaderIterator it = response.headerIterator("Set-Cookie");
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }

    /**
     * 通过  HeaderElementIterator  和nameValuePair 迭代header
     */
    public void test5() {
        HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
                HttpStatus.SC_OK, "OK");
        response.addHeader("Set-Cookie",
                "c1=a; path=/; domain=localhost");
        response.addHeader("Set-Cookie",
                "c2=b; path=\"/\", c3=c; domain=\"localhost\"");

        HeaderElementIterator it = new BasicHeaderElementIterator(
                response.headerIterator("Set-Cookie"));

        while (it.hasNext()) {
            HeaderElement elem = it.nextElement();
            System.out.println(elem.getName() + " = " + elem.getValue());
            NameValuePair[] params = elem.getParameters();
            for (int i = 0; i < params.length; i++) {
                System.out.println(" " + params[i]);
            }
    }}


    /**
     * httpClient中的entity 有三中 streamed （不可重复读）
     * self-contained（可以重复读 eg StringEntity,BytearrayEntity 等
     * wrapping 实体
     * @throws IOException
     */
    public void test6() throws IOException {
        StringEntity myEntity = new StringEntity("important message",
                ContentType.create("text/plain", "UTF-8"));

        System.out.println(myEntity.getContentType());
        System.out.println(myEntity.getContentLength());
        System.out.println(EntityUtils.toString(myEntity));
        System.out.println(EntityUtils.toByteArray(myEntity).length);
        System.out.println(myEntity.getContentType());
        System.out.println(myEntity.getContentEncoding());
    }

    /**
     * 必须要关掉连接的实体，(不仅从实体中读要释放资源，写入实体后也要释放资源)，和response 本身
     */
    @Test
    public void test7() {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpGet httpget = new HttpGet("http://www.baidu.com");
       // httpget.setHeader("cookie","jd.erp.lang=zh_CN; 3AB9D23F7A4B3C9B=64NJWJXRUSFIFDUURHVBZMED67H5NKRHNITK5GFNNADKBBPDBLXAQ6JV4ECXFEGD4WNLCWV2BAKJKHLCWTS37A2GAA;");
        CloseableHttpResponse response=null;
        try {
            response = httpclient.execute(httpget);
            System.out.println("-----"+response.getLastHeader("Content-Type").getValue());
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                InputStream instream = entity.getContent();
                try {
                    BufferedReader reader=new BufferedReader(new InputStreamReader(instream,"utf-8"));
                    String s="";
                    while((s=reader.readLine()) !=null){
                        System.out.println(s);
                    }

                } finally {
                    instream.close();
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 可以 通过 EntityUtils#consume(HttpEntity)  方法来确保流的内容消费 ，和连接被关闭（只能是stream 流）
     */
    public void test8() {

    }

    /**
     * 如果要多次读取实体，要么把内容缓冲到磁盘上，要么到内存上，
     * BufferedHttpEntity 可以缓冲到内存上
     */
    public void test9() {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpGet httpget = new HttpGet("http://www.baidu.com");
        CloseableHttpResponse response=null;
        HttpEntity entity=null;
        try {
            response= httpclient.execute(httpget);
            entity = response.getEntity();
            if (entity != null) {
                entity = new BufferedHttpEntity(entity);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                EntityUtils.consume(entity);
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }


    /**
     * 最好的方式是通过 HttpEntity#getContent() or HttpEntity#writeTo(OutputStream)
     * 来读取和写入实体，也可以通过 entityUtils，来简化实体的读取和写入实体，
     * 但是不建议这样做，除非信任实体的来源(比如实体的内容可能很大，
     * 通过entityUtils 可能占用过多的内存)。
     */
    @Test
    public void test10() {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpGet httpget = new HttpGet("https://www.baidu.com");
        CloseableHttpResponse response=null;
        try {
            response = httpclient.execute(httpget);
            HttpEntity entity = response.getEntity();

            if (entity != null) {
                long len = entity.getContentLength();
                System.out.println("----len :"+len);
                System.out.println(EntityUtils.toString(entity));
               /* if (len != -1 && len < 2048) {
                    System.out.println(EntityUtils.toString(entity));
                } else {
                    // Stream content out
                }*/
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * httpClient 提供了几个 entity 类，用来把内容封装入http 请求中
     * 比如  StringEntity, ByteArrayEntity, InputStreamEntity, and FileEntity.
     * 流实体 InputStreamEntity 是不能重复读的。。一般建议  自己实现一个 HttpEntity
     * Please note InputStreamEntity is not repeatable, because it can only read from
     * the underlying data stream once.
     * Generally it is recommended to implement a custom HttpEntity class which is
     * self-contained instead of using the generic InputStreamEntity. FileEntity can be a good starting point.
     */
    @Test
    public void test11() {
        File file = new File("somefile.txt");
        FileEntity entity = new FileEntity(file,
                ContentType.create("text/plain", "UTF-8"));

        HttpPost httppost = new HttpPost("http://localhost/action.do");
        httppost.setEntity(entity);
    }


    /**
     * 许多应用程序需要模拟提交HTML表单的过程，以便登录到web应用程序或提交输入数据。
     * HttpClient提供实体类UrlEncodedFormEntity来促进该过程
     *
     * The UrlEncodedFormEntity instance will use the so called URL encoding
     * to encode parameters and produce the following content:
     * param1=value1&param2=value2
     */
    @Test
    public void test12() {
        List<NameValuePair> formparams = new ArrayList<NameValuePair>();
        formparams.add(new BasicNameValuePair("param1", "value1"));
        formparams.add(new BasicNameValuePair("param2", "value2"));
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
        HttpPost httppost = new HttpPost("http://localhost/handler.do");
        httppost.setEntity(entity);
    }

    /**
     * 文件上传 1
     *
     * 表单提交时，参数被封装进UrlEncodedFormEntity这个类中。相似地，文件上传时，
     * 文件信息被封装到MultipartFormEntity这个类中，这两个类均实现了HttpEntity接口。
     * 为了方便实现文件上传，HttpClients提供了MultipartEntityBuilder这个工具类来生成MultipartFormEntity，
     *MultipartEntityBuilder支持三种方式来添加文件信息，分别是File文件，byte数组，InputStream文件流
    */
    @Test
    public void test122() throws IOException {
        HttpClient httpclient = HttpClients.createDefault();
        HttpPost post = new HttpPost("http://localhost:8080/Crayfish/FileUpload");
        File file = new File("F:/PIC/house.jpeg");;
        HttpEntity entity = MultipartEntityBuilder.create()
                                        .addBinaryBody("file", file, ContentType.DEFAULT_BINARY, "photo.jpg")
                                        .build();
        post.setEntity(entity);
        HttpResponse response = httpclient.execute(post);
    }

    /**
     *上传文件2 (默认)
     * @throws IOException
     */
    @Test
    public void test123() throws IOException {
        HttpClient httpclient = HttpClients.createDefault();
        HttpPost post = new HttpPost("http://localhost:8080/Crayfish/FileUpload");
        File file = new File("F:/PIC/house.jpeg");;
        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        builder.addBinaryBody("file", file);
        HttpEntity entity=builder.build();


        post.setEntity(entity);
        HttpResponse response = httpclient.execute(post);
    }
    /**
     *上传文件3 (通过字节数组的方式)
     * @throws IOException
     */
    @Test
    public void test124() throws IOException {
        HttpClient httpclient = HttpClients.createDefault();
        HttpPost post = new HttpPost("http://localhost:8080/Crayfish/FileUpload");
        File file = new File("F:/PIC/house.jpeg");

        MultipartEntityBuilder builder = MultipartEntityBuilder.create();

        FileInputStream fis = new FileInputStream(file);
        ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
        byte[] b = new byte[1000];
        int n;
        while ((n = fis.read(b)) != -1) {
            bos.write(b, 0, n);
        }
        fis.close();
        bos.close();
        byte[] byteArr = bos.toByteArray();
        builder.addBinaryBody("file", byteArr, ContentType.DEFAULT_BINARY, "photoArr.jpg");
        HttpEntity entity=builder.build();
        post.setEntity(entity);
        HttpResponse response = httpclient.execute(post);
    }

    /**
     *上传文件4 (通过io流的方式)
     * @throws IOException
     */
    @Test
    public void test125() throws IOException {
        HttpClient httpclient = HttpClients.createDefault();
        HttpPost post = new HttpPost("http://localhost:8080/Crayfish/FileUpload");
        File file = new File("F:/PIC/house.jpeg");

        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        InputStream stream = new FileInputStream(file);
        builder.addBinaryBody("file", stream, ContentType.DEFAULT_BINARY, "photoStream.jpg");
        HttpEntity entity=builder.build();


        post.setEntity(entity);
        HttpResponse response = httpclient.execute(post);
    }


    /**
     *上传文件4 (通过io流的方式，默认)
     * @throws IOException
     */
    @Test
    public void test126() throws IOException {
        HttpClient httpclient = HttpClients.createDefault();
        HttpPost post = new HttpPost("http://localhost:8080/Crayfish/FileUpload");
        File file = new File("F:/PIC/house.jpeg");

        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        InputStream stream = new FileInputStream(file);
        builder.addBinaryBody("file", stream);
        HttpEntity entity=builder.build();

        post.setEntity(entity);
        HttpResponse response = httpclient.execute(post);
    }









    /**
     * 一般http协议传输时可以自动默认选一个编码方式，但是可以指定http协议的编码方式（http1.0不适用）
     *
     *
     * Generally it is recommended to let HttpClient choose the most appropriate transfer encoding based
     * on the properties of the HTTP message being transferred. It is possible, however,
     * to inform HttpClient that chunk coding is preferred by setting HttpEntity#setChunked() to true.
     * Please note that HttpClient will use this flag as a hint only.
     * This value will be ignored when using HTTP protocol versions that do not support chunk coding, such as HTTP/1.0.
     */
    public void test13() {
        StringEntity entity = new StringEntity("important message",
                ContentType.create("plain/text", Consts.UTF_8));
        entity.setChunked(true);
        HttpPost httppost = new HttpPost("http://localhost/acrtion.do");
        httppost.setEntity(entity);
    }

    /**
     * 最好的处理 response 的方式是使用  ResponseHandler
     * 其可以自动的管理连接，释放连接，不论是否出现异常
     */
    @Test
    public void test14() {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpGet httpget = new HttpGet("http://www.baidu.com");

        ResponseHandler<String> rh = new ResponseHandler<String>() {

            @Override
            public String handleResponse(
                    final HttpResponse response) throws IOException {
                StatusLine statusLine = response.getStatusLine();
                HttpEntity entity = response.getEntity();
                if (statusLine.getStatusCode() >= 300) {
                    throw new HttpResponseException(
                            statusLine.getStatusCode(),
                            statusLine.getReasonPhrase());
                }
                if (entity == null) {
                    throw new ClientProtocolException("Response contains no content");
                }
                ContentType contentType = ContentType.getOrDefault(entity);
                //注意编码为null
                System.out.println("====================="+entity.getContentEncoding());
                Charset charset = contentType.getCharset();
                Reader reader = new InputStreamReader(entity.getContent(), "utf-8");
                System.out.println(new BufferedReader(reader).readLine());
               // return JSONObject.toJSON(reader);
                return "aaa";
            }
        };
        try {
            String myjson = httpclient.execute(httpget, rh);
            System.out.println(myjson);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * httpClient 添加客户自定义的一些实现，
     * httpClent是线程安全的，可以重用
     *
     *
     */
    @Test
    public void test15() {
        ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() {

            @Override
            public long getKeepAliveDuration(
                    HttpResponse response,
                    HttpContext context) {
                long keepAlive = super.getKeepAliveDuration(response, context);
                if (keepAlive == -1) {
                    // Keep connections alive 5 seconds if a keep-alive value
                    // has not be explicitly set by the server
                    keepAlive = 5000;
                }
                return keepAlive;
            }

        };
        CloseableHttpClient httpclient = HttpClients.custom()
                .setKeepAliveStrategy(keepAliveStrat)
                .build();
    }

    /**
     * 当不需要httpClient的时， 要将其关闭
     */
    @Test
    public void test16() {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        try {
        }catch (Exception e){

        } finally {
            try {
                httpclient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 连接超时设置
     * 4.5版本后，超时设置都放在了  RequestConfig 中
     * see  https://blog.csdn.net/u011191463/article/details/78664896
     */
    @Test
    public void test161() throws IOException {
        RequestConfig requestConfig = RequestConfig.custom()
                //设置连接超时时间，单位毫秒, 指的是连接目标url的连接超时时间，
                // 即客服端发送请求到与目标url建立起连接的最大时间。如果在该时间范围内还没有建立起连接，则就
                // 抛出connectionTimeOut异常。如测试的时候，将url改为一个不存在的url：“http://test.com” ，
                // 超时时间3000ms过后，系统报出异常：
                // org.apache.commons.base.httpclient.ConnectTimeoutException:The host did not accept the connection within timeout of 3000 ms
                .setConnectTimeout(1000)
                //设置从connect Manager(连接池)获取Connection 超时时间，单位毫秒。
                // 这个属性是新加的属性，因为目前版本是可以共享连接池的,HttpClient中的要用连接时尝试从连接池中获取，
                // 若是在等待了一定的时间后还没有获取到可用连接（比如连接池中没有空闲连接了）则会抛出获取连接超时异常

                .setConnectionRequestTimeout(1000)
                //请求获取数据的超时时间(即响应时间)，单位毫秒。 如果访问一个接口，多少时间内无法返回数据，
                // 就直接放弃此次调用。 连接上一个url后，获取response的返回等待时间 ，即在与目标url建立连接后，
                // 等待放回response的最大时间，在规定时间内没有返回响应的话就抛出SocketTimeout
                .setSocketTimeout(1000)
                .build();

        CloseableHttpClient httpClient = HttpClients.custom()
                //连接池中的最大连接数
                .setMaxConnTotal(200)
                //每一个路由的最大连接数，比如，baidu.com 和jd.com就是两个路由
                .setMaxConnPerRoute(10)
                //这个config 可以放到httpClient中作为默认配置，也可以为每个httpGet或httpPost等请求单独设置
                //见 （3） 处
                .setDefaultRequestConfig(requestConfig)
                .build();


        HttpGet httpGet = new HttpGet("https://www.jd.com");
        //(3)
        httpGet.setConfig(requestConfig);
        CloseableHttpResponse response1 = httpClient.execute(httpGet);
    }



    /**
     * 多个http 请求可以共用httpContext 上下文
     * 最初，Http被设计成一种无状态的、面向请求-响应的协议。然而，在实际使用中，
     * 我们希望能够在一些逻辑相关的请求-响应中，保持状态信息。为了使应用程序可以保持Http的持续状态，HttpClient允许http连接在特定的Http上下文中执行。如果在持续的http请求中使用了同样的上下文，那么这些请求就可以被分配到一个逻辑会话中。HTTP上下文就和一个java.util.Map<String, Object>功能类似。它实际上就是一个任意命名的值的集合。应用程序可以在Http请求执行前填充上下文的值，也可以在请求执行完毕后检查上下文。
     HttpContext可以包含任意类型的对象，因此如果在多线程中共享上下文会不安全。
     推荐每个线程都只包含自己的http上下文。
     在Http请求执行的过程中，HttpClient会自动添加下面的属性到Http上下文中：
     HttpConnection的实例，表示客户端与服务器之间的连接
     HttpHost的实例，表示要连接的木包服务器
     HttpRoute的实例，表示全部的连接路由
     HttpRequest的实例，表示Http请求。在执行上下文中，最终的HttpRequest对象会代表http消息的状态。
     Http/1.0和Http/1.1都默认使用相对的uri。但是如果使用了非隧道模式的代理服务器，就会使用绝对路径的uri。
     HttpResponse的实例，表示Http响应
     java.lang.Boolean对象，表示是否请求被成功的发送给目标服务器
     RequestConfig对象，表示http request的配置信息
     java.util.List<Uri>对象，表示Http响应中的所有重定向地址
     我们可以使用HttpClientContext这个适配器来简化和上下文交互的过程
     同一个逻辑会话中的多个Http请求，应该使用相同的Http上下文来执行，这样就可以自动地在http请求中传递会话上下文和状态信息。
     在下面的例子中，我们在开头设置的参数，会被保存在上下文中，并且会应用到后续的http请求中（源英文中有个拼写错误）。
     */
    @Test
    public void test17() throws IOException {

        //
        HttpContext context= HttpClientContext.create();
        context.setAttribute("aaaaaa",123);

        CloseableHttpClient httpclient = HttpClients.createDefault();
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(1000)
                .setConnectTimeout(1000)
                .build();

        HttpGet httpget1 = new HttpGet("https://www.jd.com");
        httpget1.setConfig(requestConfig);
        CloseableHttpResponse response1 = httpclient.execute(httpget1, context);
        try {
            HttpEntity entity1 = response1.getEntity();
        } finally {
            try {
                response1.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        HttpGet httpget2 = new HttpGet("https://www.jd.com");
        CloseableHttpResponse response2 = httpclient.execute(httpget2, context);
        try {
            HttpEntity entity2 = response2.getEntity();
        } finally {
            try {
                response2.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * http协议拦截器，协议拦截器的实现必须要是线程安全的
     * 这是一个例子，说明如何使用本地上下文在连续请求之间持久化处理状态
     */

    @Test
    public void test18() throws IOException {
        CloseableHttpClient httpclient = HttpClients.custom()
                .addInterceptorLast(new HttpRequestInterceptor() {

                    public void process(
                            final HttpRequest request,
                            final HttpContext context) throws HttpException, IOException {
                        //2获取设置到httpContext中的变量
                        AtomicInteger count = (AtomicInteger) context.getAttribute("count");
                        request.addHeader("Count", Integer.toString(count.getAndIncrement()));
                        System.out.println("count----"+count.get());
                    }

                })
                .build();

        //1，请求前把 count 设置进  httpContext中
        AtomicInteger count = new AtomicInteger(1);
        HttpClientContext localContext = HttpClientContext.create();
        localContext.setAttribute("count", count);

        HttpGet httpget = new HttpGet("https://www.baidu.com");
        CloseableHttpResponse response = httpclient.execute(httpget, localContext);
            try {
                HttpEntity entity = response.getEntity();
            } finally {
                response.close();
        }
    }

    /**
     * http的重试
     *为了启用自定义异常恢复机制，应该提供HttpRequestRetryHandler接口的实现
     */
    @Test
    public void test19() {
        HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {

            public boolean retryRequest(
                    IOException exception,
                    int executionCount,
                    HttpContext context) {
                if (executionCount >= 5) {
                    // Do not retry if over max retry count
                    return false;
                }
                if (exception instanceof InterruptedIOException) {
                    // Timeout
                    return false;
                }
                if (exception instanceof UnknownHostException) {
                    // Unknown host
                    return false;
                }
                if (exception instanceof ConnectTimeoutException) {
                    // Connection refused
                    return false;
                }
                if (exception instanceof SSLException) {
                    // SSL handshake exception
                    return false;
                }
                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
                if (idempotent) {
                    // Retry if the request is considered idempotent
                    return true;
                }
                return false;
            }

        };
        CloseableHttpClient httpclient = HttpClients.custom()
                .setRetryHandler(myRetryHandler)
                .build();
    }

    /**
     * 在某些情况下，由于目标服务器上的高负载或客户机端发出的并发请求太多，
     * HTTP请求执行无法在预期的时间框架内完成。在这种情况下，
     * 可能需要提前终止请求并解除I/O操作中阻塞的执行线程。通过调用HttpUriRequest#abort()方法，(httpget等的父类)
     * HttpClient正在执行的HTTP请求可以在执行的任何阶段中止。该方法是线程安全的，可以从任何线程调用。
     * 当HTTP请求中止其执行线程时——即使当前在I/O中被阻塞
     * (是否可以通过后台线程，和delayQueue 到期时终止线程)？？
     */
    public void test20() {

    }

    /**
     * HttpClient自动处理所有类型的重定向，除非HTTP规范明确禁止用户进行干预。
     * 请参阅POST和PUT请求上的其他重定向(状态代码303)，这些重定向请求将根据HTTP规范的要求转换为GET请求。
     * 可以使用自定义重定向策略来放松HTTP规范对POST方法自动重定向的限制
     *
     * 可以通过  httpGet.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build()); 禁止从定向
     */
    public void test21() {
        //可以看下这个类的源码所说的，对很多方法自动重定向，而其的父类，只对几个方法进行了重定向
        LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
        CloseableHttpClient httpclient = HttpClients.custom()
                .setRedirectStrategy(redirectStrategy)
                .build();
    }

    /**
     * 重定向网址中 获取最终的url
     * @throws IOException
     * @throws URISyntaxException
     */
    @Test
    public void test22() throws IOException, URISyntaxException {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpClientContext context = HttpClientContext.create();
        HttpGet httpget = new HttpGet("http://afslocal.jd.com:8080/");
        CloseableHttpResponse response = httpclient.execute(httpget, context);

        HttpHost target = context.getTargetHost();
        //从上下文中获取重定向链接
        List<URI> redirectLocations = context.getRedirectLocations();
        //获取到最终的链接
        URI location = URIUtils.resolve(httpget.getURI(), target, redirectLocations);
        System.out.println("Final HTTP location: " + location.toASCIIString());
        // Expected to be an absolute URI
        response.close();
    }

    /**
     * http 链接管理器  httpClientConnectionManager()
     *
     * The connection request can be terminated prematurely by calling ConnectionRequest#cancel() if necessary.
     * This will unblock the thread blocked in the ConnectionRequest#get() method
     *
     * BasicHttpClientConnectionManager is a simple connection manager that maintains
     * only one connection at a time. Even though this class is thread-safe it ought
     * to be used by one execution thread only. BasicHttpClientConnectionManager will
     * make an effort to reuse the connection for subsequent requests with the same route.
     * It will, however, close the existing connection and re-open it for the given route,
     * if the route of the persistent connection does not match that of the connection request.
     * If the connection has been already been allocated, then java.lang.IllegalStateException is thrown.
     *
     *This connection manager implementation should be used inside an EJB container
     *
     */
    @Test
    public void test23() throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {
        //1,context 上下文
        HttpClientContext context = HttpClientContext.create();

        //BasicHttpClientConnectionManager 只维护一个连接，会重用 相同路由的 request 连接
        HttpClientConnectionManager connMrg = new BasicHttpClientConnectionManager();
        HttpRoute route = new HttpRoute(new HttpHost("http://www.baidu.com"));
        // Request new connection. This can be a long process
        ConnectionRequest connRequest = connMrg.requestConnection(route, null);
        // Wait for connection up to 10 sec
        HttpClientConnection conn = connRequest.get(10, TimeUnit.SECONDS);

        try {
            // If not open
            if (!conn.isOpen()) {
                // establish connection based on its route info
                connMrg.connect(conn, route, 1000, context);
                // and mark it as route complete
                connMrg.routeComplete(conn, route, context);
            }
            // Do useful things with the connection.
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            connMrg.releaseConnection(conn, null, 1, TimeUnit.MINUTES);
        }
    }

    /**
     * PoolingHttpClientConnectionManager  实现类 提供默认 2 个并发连接，20个最大连接
     */
    @Test
    public void test24() {
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        // Increase max total connection to 200
        cm.setMaxTotal(200);
        // Increase default max connection per route to 20
        cm.setDefaultMaxPerRoute(20);
        // Increase max connections for localhost:80 to 50
        HttpHost localhost = new HttpHost("locahost", 80);
        cm.setMaxPerRoute(new HttpRoute(localhost), 50);

        CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(cm)
                .build();
    }

    /**
     * 单不需要的时候可以通过 base.httpclient.close 来关闭连接（base.httpclient 可以重用，相当关闭所有的了）
     */
    public void test25() throws IOException {
        CloseableHttpClient httpClient =HttpClients.custom().build();
        httpClient.close();
    }

    /**
     * 当配置池连接管理器(如PoolingClientConnectionManager)时，HttpClient可以使用多个执行线程同时执行多个请求
     */
    @Test
    public void test26() throws InterruptedException {
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(cm)
                .build();

        // URIs to perform GETs on
        String[] urisToGet = {
                "http://www.baidu.com/",
                "http://www.jd.com/",
                "http://csdn.net/",
                "http://163.com/"
        };

        for(int i=0;i<4;i++){
            HttpGet httpGet=new HttpGet(urisToGet[i]);
            new Thread(new MyThread(httpClient,httpGet)).start();
        }


        Thread.sleep(5000);

    }

    /**
     * 尽管httpClient是线程安全的，但还是强烈建议 没个线程中获取自己的
     * httpContext
     */
    @Test
    public void test27() {
        CloseableHttpClient httpClient=HttpClients.createDefault();
        HttpGet httpGet=new HttpGet("www.baidu.com");
        new Thread(new MyThread2(httpClient,httpGet));
    }

    /**
     * see  https://xls9577087.iteye.com/blog/2268698
     *关闭过期的连接要指定过期策略,来指明哪些是过期的   see  ConnectionKeepAliveStrategy
     *
     *
     * 经典阻塞I/O模型的一个主要缺点就是只有当组侧I/O时，socket才能对I/O事件做出反应。
     * 当连接被管理器收回后，这个连接仍然存活，但是却无法监控socket的状态，也无法对I/O事件做出反馈。
     * 如果连接被服务器端关闭了，客户端监测不到连接的状态变化（也就无法根据连接状态的变化，关闭本地的socket）。
     HttpClient为了缓解这一问题造成的影响，会在使用某个连接前，监测这个连接是否已经过时，
     如果服务器端关闭了连接，那么连接就会失效。这种过时检查并不是100%有效，并且会给每个请求增加10到30毫秒额外开销。
     唯一一个可行的，且does not involve a one thread per socket model for idle connections的解决办法，
     是建立一个监控线程，来专门回收由于长时间不活动而被判定为失效的连接。
     这个监控线程可以周期性的调用ClientConnectionManager类的closeExpiredConnections()方法来关闭过期的连接，
     回收连接池中被关闭的连接。它也可以选择性的调用ClientConnectionManager类的closeIdleConnections()方法来
     关闭一段时间内不活动的连接。
     */
    @Test
    public void test28() {
        PoolingHttpClientConnectionManager manager=new PoolingHttpClientConnectionManager();
        IdleConnectionMonitorThread idleConnectionMonitorThread=new IdleConnectionMonitorThread(manager);
    }

    /**
     *Http规范没有规定一个持久连接应该保持存活多久。有些Http服务器使用非标准的Keep-Alive头消息和客户端进行交互，
     * 服务器端会保持数秒时间内保持连接。HttpClient也会利用这个头消息。如果服务器返回的响应中没有包含Keep-Alive头消息，
     * HttpClient会认为这个连接可以永远保持。然而，很多服务器都会在不通知客户端的情况下，关闭一定时间内不活动的连接，
     * 来节省服务器资源。在某些情况下默认的策略显得太乐观，我们可能需要自定义连接存活策略
     */
    public void test29() {
        ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {

            public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                // Honor 'keep-alive' header
                HeaderElementIterator it = new BasicHeaderElementIterator(
                        response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                while (it.hasNext()) {
                    HeaderElement he = it.nextElement();
                    String param = he.getName();
                    String value = he.getValue();
                    if (value != null && param.equalsIgnoreCase("timeout")) {
                        try {
                            return Long.parseLong(value) * 1000;
                        } catch(NumberFormatException ignore) {
                        }
                    }
                }
                HttpHost target = (HttpHost) context.getAttribute(
                        HttpClientContext.HTTP_TARGET_HOST);
                if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) {
                    // Keep alive for 5 seconds only
                    return 5 * 1000;
                } else {
                    // otherwise keep alive for 30 seconds
                    return 30 * 1000;
                }
            }

        };
        CloseableHttpClient client = HttpClients.custom()
                .setKeepAliveStrategy(myStrategy)
                .build();
    }

    /**
     * socket相关，跳过
     */
    public void test30() {

    }

    /**
     * CookieSpec接口代表了Cookie管理规范。Cookie管理规范规定了：

     解析Set-Cookie和Set-Cookie2(可选）头消息的规则
     验证Cookie的规则
     将指定的主机名、端口和路径格式化成Cookie头消息
     HttpClient有下面几种CookieSpec规范：

     Netscape draft: 这种符合网景公司指定的规范。但是尽量不要使用，除非一定要保证兼容很旧的代码。
     Standard: RFC 2965 HTTP状态管理规范
     Browser compatibility: 这种方式，尽量模仿常用的浏览器，如IE和firefox
     Best match: ‘Meta’ cookie specification that picks up a cookie policy based on the format of cookies sent with the HTTP response.它基本上将上面的几种规范积聚到一个类中。
     ++ Ignore cookies: 忽略所有Cookie
     强烈推荐使用Best Match(4.4以后改为default)匹配规则，让HttpClient根据运行时环境自己选择合适的规范
     *
     *我们可以在创建Http client的时候指定Cookie测试，如果需要，也可以在执行http请求的时候，进行覆盖指定
     */
    public void test50() {
        RequestConfig globalConfig = RequestConfig.custom()
                .setCookieSpec(CookieSpecs.DEFAULT)
                .build();
        CloseableHttpClient httpclient = HttpClients.custom()
                .setDefaultRequestConfig(globalConfig)
                .build();
        RequestConfig localConfig = RequestConfig.copy(globalConfig)
                .setCookieSpec(CookieSpecs.DEFAULT)
                .build();
        HttpGet httpGet = new HttpGet("https://www.yeetrack.com");
        httpGet.setConfig(localConfig);
    }

    /**
     * 获取cookie 1
     * 有两种方式使用cookie
     * 1,一种是使用CookieStore实例
     *版本version 0 表示网景公司的cookie标准，1 表示标准的cookie标准
     */
    @Test
    public void test51() throws IOException {
        CookieStore cookieStore=new BasicCookieStore();
        CloseableHttpClient httpClient = HttpClients.custom().setDefaultCookieStore(cookieStore).build();
        HttpGet get=new HttpGet("www.jd.com");
        httpClient.execute(get);
        List<Cookie> cookies=cookieStore.getCookies();//获取Cookie

        for(Cookie cookie:cookies){
            System.out.println(cookie.getName()+" : "+cookie.getValue());
        }
    }

    /**
     * 获取cookie2
     *
     * 另一种是使用HttpClientContext，使用内置CookieStore
     */
    public void test52() throws IOException {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet get=new HttpGet("http://www.baidu.com/");
        HttpClientContext context=HttpClientContext.create();
        httpClient.execute(get,context);
        context.getCookieStore().getCookies();//获取Cookie
    }

    /**
     * 发送cookie 1
     */
    @Test
    public void test53() throws IOException {
        BasicCookieStore cookieStore  = new BasicCookieStore();
        BasicClientCookie cookie1 =
                new BasicClientCookie("erp1.jd.com","05A9EAAC02F3539F1627C4799F013740B68C397E8B3D6E88967F2C40FC55794856A0685C2849732C3ED8F086018697CBCB621AD42C29EEDF3B591B93B7CA1C3FA760E9F7364162EE8F7CDDD18F774297");

        cookie1.setVersion(1);
        //注意这个域名
        cookie1.setDomain(".jd.com");
        cookie1.setPath("/");
        cookieStore.addCookie(cookie1);

        BasicClientCookie cookie2 = new BasicClientCookie("sso.jd.com","6ca720f2473c4237a6f4781b14c85b5d");

        cookie2.setVersion(1);
        cookie2.setDomain(".jd.com");
        cookie2.setPath("/");
        cookieStore.addCookie(cookie2);

        HttpClient httpClient=HttpClients.custom().setDefaultCookieStore(cookieStore)
                //通过协议拦截器添加header
                .addInterceptorLast(new HttpRequestInterceptor() {

                    public void process(
                            final HttpRequest request,
                            final HttpContext context) throws HttpException, IOException {
                        request.addHeader("Referer","http://cpl.jd.com/");
                    }

                }).build();
        //获取所有cookie
        //List<Cookie> list = cookieStore.getCookies();
        // 清除所有cookie
        //cookieStore.clear();



        // cookie.setAttribute(ClientCookie.VERSION_ATTR, "0");
        // cookie.setAttribute(ClientCookie.DOMAIN_ATTR, "127.0.0.1");
        // cookie.setAttribute(ClientCookie.PORT_ATTR, "8080");
        // cookie.setAttribute(ClientCookie.PATH_ATTR, "/CwlProWeb");
        HttpGet httpGet2=new HttpGet("http://cpl.jd.com/icGstsQuery/query?complaintSrc=2");
        HttpResponse response2=httpClient.execute(httpGet2);
        System.out.println(EntityUtils.toString(response2.getEntity()));


    }

    /**
     * 发送cookie2
     */
    public void test54() {

    }
    /**
     * 登录，获取登录后的cookie
     */
    @Test
    public void test55() throws IOException {
        //1,登录
        List<NameValuePair> formparams = new ArrayList<NameValuePair>();
        formparams.add(new BasicNameValuePair("username", "majian8"));
        formparams.add(new BasicNameValuePair("password", "%%MAanan3210"));
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
        HttpPost httppost = new HttpPost("http://ssa.jd.com/sso/login?ReturnUrl=http%3A%2F%2Fcpl.jd.com%2F");
        httppost.setEntity(entity);

        BasicCookieStore cookieStore=new BasicCookieStore();
        HttpClient httpClient=HttpClients.custom().setDefaultCookieStore(cookieStore).build();

        HttpResponse response=httpClient.execute(httppost);

        List<Cookie> cookies=cookieStore.getCookies();//获取Cookie

        for(Cookie cookie:cookies){
            System.out.println(cookie.getName()+" : "+cookie.getValue());
        }

        //2，登录后，获取的cookie后存入了cookieStore,只要这个httpClient不关闭，就一直可以会话保存
        //不用在每个httpGet 或httpPost中写入cookie了

        HttpGet httpGet=new HttpGet("http://cpl.jd.com/icGstsQuery/query?complaintSrc=2");
        httpGet.setHeader("Referer","http://cpl.jd.com/");
        HttpResponse response2=httpClient.execute(httpGet);
        System.out.println(EntityUtils.toString(response2.getEntity()));


    }

    /**
     * 设置代理（通过fiddler 代理端口访问）
     */
    @Test
    public void test56() throws IOException {
        HttpHost proxy = new HttpHost("127.0.0.1", 8888, "http");
        DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
        HttpClient httpClient=HttpClients.custom()
                .setRoutePlanner(routePlanner)
                .build();

        HttpGet httpGet=new HttpGet("http://cpl.jd.com");
        HttpResponse response2=httpClient.execute(httpGet);
        System.out.println(EntityUtils.toString(response2.getEntity()));
    }

    public void test() {

    }



}
